//	Lexer.java

import java.io.*;

/** 
Description: Lexer class for Minimal grammar given at
<a href="http://cs.boisestate.edu/~tcole/cs354/spr05/grammar0.html">
http://cs.boisestate.edu/~tcole/cs354/spr05/grammar0.html</a>

<p>The Lexer class reads the input one character at a time.  It returns the
input data one Lexeme at a time.  The class that uses the Lexer should first
call the init method to set up the input and then call next() each time another
Lexeme is needed.

@author Teresa Cole
@author Samuel Jenkins
@version CS354 Fall 2005 - modified Fall 2008
 */
public class Lexer implements Token
{

    private int lexType,  intValue;
    private String stringValue;
    private InputStreamReader in;
    private char lastChar = '\0';
    /** atEOF will be true when the end of the file has been reached. */
    public boolean atEOF = false;

    /**  Initializes the InputStreamReader from which characters will be read.
     */
    public Lexer(InputStream ins)
    {
        in = new InputStreamReader(ins);
    }

    /**  Returns the next available Lexeme
    
    @return the next Lexeme in the input stream
     */
    public Lexeme next()
    {
        // System.out.println( "Lexeme.next");
        
        try
        {
            if (lastChar == '\0' || Character.isWhitespace(lastChar))
            {
                skipSpace();
            }
            switch (lastChar)
            {
                case '+':
                    lastChar = '\0';
                    return new Lexeme(PLUS);
                case '-':
                    lastChar = '\0';
                    return new Lexeme(MINUS);
                case '*':
                    lastChar = '\0';
                    return new Lexeme(TIMES);
                case '/':
                    lastChar = '\0';
                    return new Lexeme(DIVIDE);
                case ';':
                    lastChar = '\0';
                    return new Lexeme(SEMIC);
                case '(':
                    lastChar = '\0';
                    return new Lexeme(OPAREN);
                case ')':
                    lastChar = '\0';
                    return new Lexeme(CPAREN);
                case '=':
                    lastChar = '\0';
                    return new Lexeme(ASSIGN);
            }

            if (String.valueOf(lastChar).matches("[0-9.]"))
            {
                return getNumber();
            } else if (Character.isLetter(lastChar) || lastChar == '_')
            {
                return getWord();
            } else
            {
                lastChar = '\0';
                return new Lexeme(UNKNOWN);
            }
        } catch (Exception e)
        {
            if (e instanceof EOFException)
            {
                return new Lexeme(EOF);
            } else
            {
                System.err.println("Error in next():\n" + e);
                e.printStackTrace(System.err);
                exit();
            }
        }
        return new Lexeme(UNKNOWN, "" + lastChar);
    }

    /**  Builds up a String from the input.  The String will start with a letter
     * and end before the next character in the input that is not alphanumeric.
     */
    private Lexeme getWord() throws SyntaxError
    {
//    System.out.println( "Lexeme.getWord");
        String word = "" + lastChar;
        try
        {
            readChar();
            while (lastChar == '_' || Character.isLetter(lastChar))
            {
                word += lastChar;
                readChar();
            }
        } catch (Exception e)
        {
            if (e instanceof EOFException)
            {
                atEOF = true;
            } else
            {
                System.err.println("Error in getWord:\n" + e);
                exit();
            }
        }
        return keyword(word);
    }

    /**  Builds up an integer from the input.  The number will contain the 
     * sequence of digits in the input that ends before the next non-digit.
     */
    private Lexeme getNumber()
    {
//    System.out.println( "Lexeme.getNumber");
        String num = "" + lastChar;
        try
        {
            
            readChar();
            while (String.valueOf(lastChar).matches("[0-9.eE-]{1}"))
            {
                num += lastChar;
                readChar();
            }
            if (!num.matches("([0-9]+.?[0-9]*|.[0-9]+)([eE](-)?[0-9]+)?"))
            {
                throw new Exception();
            }
        } catch (Exception e)
        {
            if (e instanceof EOFException)
            {
                atEOF = true;
            } else
            {
                System.err.println("Error in getNumber:\n" + e);
                exit();
            }
        }
        return new Lexeme(NUMBER, Float.parseFloat(num));
    }

    /**  Checks to see whether the String is a keyword.
    
    @param word a String that is either a keyword or an identifier
    @return a Lexeme with th appropriate Token
     */
    private Lexeme keyword(String word)
    {
//    System.out.println( "Lexeme.read");
        return new Lexeme(VAR, word);
    }

    /**  Reads a single character from the input.
     */
    private void readChar() throws EOFException
    {
//    System.out.print( "Lexeme.readChar: ");
        int charIn = 0;
        try
        {
            charIn = in.read();
        } catch (IOException e)
        {
            System.err.println("Lexer.readChar()\n" + e);
            exit();
        }
        if (charIn == -1)
        {
            atEOF = true;
            throw new EOFException("EOF in Lexer.readChar()");
        } else
        {
            lastChar = (char) charIn;
        //System.out.println( lastChar);
        }
    }

    /**  Reads characters until a non-space character is encountered.  The last
     * character read will be stored in lastChar.
    
     */
    private void skipSpace() throws Exception
    {
        do
        {
            readChar();
        } while (Character.isWhitespace(lastChar));
    }
    protected void exit()
    {
        System.exit(1);
    }
}
